/*****************************************************************************
 * Free42 -- an HP-42S calculator simulator
 * Copyright (C) 2004-2011  Thomas Okken
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see http://www.gnu.org/licenses/.
 *****************************************************************************/

#include <stdlib.h>

#include "core_globals.h"
#include "core_helpers.h"
#include "core_display.h"
#include "core_variables.h"


// We cache vartype_real, vartype_complex, and vartype_string instances, to
// cut down on the malloc/free overhead. This overhead is particularly painful
// in the PalmOS ARM version, because it has to do an ARM-to-68K call for each
// malloc or free, and that's a performance killer when running programs.
// TODO: Pools may cause memory fragmentation. To fix, override malloc() and
// realloc() with versions that empty the pools and retry before returning
// NULL.

typedef struct pool_real {
    vartype_real r;
    struct pool_real *next;
} pool_real;

static pool_real *realpool = NULL;

typedef struct pool_complex {
    vartype_complex c;
    struct pool_complex *next;
} pool_complex;

static pool_complex *complexpool = NULL;

typedef struct pool_string {
    vartype_string s;
    struct pool_string *next;
} pool_string;

static pool_string *stringpool = NULL;

vartype *new_real(phloat value) {
    pool_real *r;
    if (realpool == NULL) {
	r = (pool_real *) malloc(sizeof(pool_real));
	if (r == NULL)
	    return NULL;
	r->r.type = TYPE_REAL;
    } else {
	r = realpool;
	realpool = realpool->next;
    }
    r->r.x = value;
    return (vartype *) r;
}

vartype *new_complex(phloat re, phloat im) {
    pool_complex *c;
    if (complexpool == NULL) {
	c = (pool_complex *) malloc(sizeof(pool_complex));
	if (c == NULL)
	    return NULL;
	c->c.type = TYPE_COMPLEX;
    } else {
	c = complexpool;
	complexpool = complexpool->next;
    }
    c->c.re = re;
    c->c.im = im;
    return (vartype *) c;
}

vartype *new_string(const char *text, int length) {
    pool_string *s;
    if (stringpool == NULL) {
	s = (pool_string *) malloc(sizeof(pool_string));
	if (s == NULL)
	    return NULL;
	s->s.type = TYPE_STRING;
    } else {
	s = stringpool;
	stringpool = stringpool->next;
    }
    int i;
    s->s.type = TYPE_STRING;
    s->s.length = length > 6 ? 6 : length;
    for (i = 0; i < s->s.length; i++)
	s->s.text[i] = text[i];
    return (vartype *) s;
}

vartype *new_realmatrix(int4 rows, int4 columns) {
    vartype_realmatrix *rm = (vartype_realmatrix *)
					malloc(sizeof(vartype_realmatrix));
    if (rm == NULL)
	return NULL;
    int4 i, sz;
    rm->type = TYPE_REALMATRIX;
    rm->rows = rows;
    rm->columns = columns;
    sz = rows * columns;
    rm->array = (realmatrix_data *) malloc(sizeof(realmatrix_data));
    if (rm->array == NULL) {
	free(rm);
	return NULL;
    }
    rm->array->data = (phloat *) malloc(sz * sizeof(phloat));
    if (rm->array->data == NULL) {
	/* Oops */
	free(rm->array);
	free(rm);
	return NULL;
    }
    rm->array->is_string = (char *) malloc(sz);
    if (rm->array->is_string == NULL) {
	/* Oops */
	free(rm->array->data);
	free(rm->array);
	free(rm);
	return NULL;
    }
    for (i = 0; i < sz; i++)
	rm->array->data[i] = 0;
    for (i = 0; i < sz; i++)
	rm->array->is_string[i] = 0;
    rm->array->refcount = 1;
    return (vartype *) rm;
}

vartype *new_complexmatrix(int4 rows, int4 columns) {
    vartype_complexmatrix *cm = (vartype_complexmatrix *)
					malloc(sizeof(vartype_complexmatrix));
    if (cm == NULL)
	return NULL;
    int4 i, sz;
    cm->type = TYPE_COMPLEXMATRIX;
    cm->rows = rows;
    cm->columns = columns;
    sz = rows * columns * 2;
    cm->array = (complexmatrix_data *) malloc(sizeof(complexmatrix_data));
    if (cm->array == NULL) {
	free(cm);
	return NULL;
    }
    cm->array->data = (phloat *) malloc(sz * sizeof(phloat));
    if (cm->array->data == NULL) {
	/* Oops */
	free(cm->array);
	free(cm);
	return NULL;
    }
    for (i = 0; i < sz; i++)
	cm->array->data[i] = 0;
    cm->array->refcount = 1;
    return (vartype *) cm;
}

vartype *new_matrix_alias(vartype *m) {
    if (m->type == TYPE_REALMATRIX) {
	vartype_realmatrix *rm1 = (vartype_realmatrix *) m;
	vartype_realmatrix *rm2 = (vartype_realmatrix *)
					malloc(sizeof(vartype_realmatrix));
	if (rm2 == NULL)
	    return NULL;
	*rm2 = *rm1;
	rm2->array->refcount++;
	return (vartype *) rm2;
    } else if (m->type == TYPE_COMPLEXMATRIX) {
	vartype_complexmatrix *cm1 = (vartype_complexmatrix *) m;
	vartype_complexmatrix *cm2 = (vartype_complexmatrix *)
					malloc(sizeof(vartype_complexmatrix));
	if (cm2 == NULL)
	    return NULL;
	*cm2 = *cm1;
	cm2->array->refcount++;
	return (vartype *) cm2;
    } else
	return NULL;
}

void free_vartype(vartype *v) {
    if (v == NULL)
	return;
    switch (v->type) {
	case TYPE_REAL: {
	    pool_real *r = (pool_real *) v;
	    r->next = realpool;
	    realpool = r;
	    break;
	}
	case TYPE_COMPLEX: {
	    pool_complex *c = (pool_complex *) v;
	    c->next = complexpool;
	    complexpool = c;
	    break;
	}
	case TYPE_STRING: {
	    pool_string *s = (pool_string *) v;
	    s->next = stringpool;
	    stringpool = s;
	    break;
	}
	case TYPE_REALMATRIX: {
	    vartype_realmatrix *rm = (vartype_realmatrix *) v;
	    if (--(rm->array->refcount) == 0) {
		free(rm->array->data);
		free(rm->array->is_string);
		free(rm->array);
	    }
	    free(rm);
	    break;
	}
	case TYPE_COMPLEXMATRIX: {
	    vartype_complexmatrix *cm = (vartype_complexmatrix *) v;
	    if (--(cm->array->refcount) == 0) {
		free(cm->array->data);
		free(cm->array);
	    }
	    free(cm);
	    break;
	}
    }
}

void clean_vartype_pools() {
    while (realpool != NULL) {
	pool_real *r = realpool;
	realpool = r->next;
	free(r);
    }
    while (complexpool != NULL) {
	pool_complex *c = complexpool;
	complexpool = c->next;
	free(c);
    }
    while (stringpool != NULL) {
	pool_string *s = stringpool;
	stringpool = s->next;
	free(s);
    }
}

vartype *dup_vartype(const vartype *v) {
    if (v == NULL)
	return NULL;
    switch (v->type) {
	case TYPE_REAL: {
	    vartype_real *r = (vartype_real *) v;
	    return new_real(r->x);
	}
	case TYPE_COMPLEX: {
	    vartype_complex *c = (vartype_complex *) v;
	    return new_complex(c->re, c->im);
	}
	case TYPE_REALMATRIX: {
	    vartype_realmatrix *rm = (vartype_realmatrix *) v;
	    vartype_realmatrix *rm2 = (vartype_realmatrix *)
					malloc(sizeof(vartype_realmatrix));
	    if (rm2 == NULL)
		return NULL;
	    rm2->type = TYPE_REALMATRIX;
	    rm2->rows = rm->rows;
	    rm2->columns = rm->columns;
	    rm2->array = rm->array;
	    rm->array->refcount++;
	    return (vartype *) rm2;
	}
	case TYPE_COMPLEXMATRIX: {
	    vartype_complexmatrix *cm = (vartype_complexmatrix *) v;
	    vartype_complexmatrix *cm2 = (vartype_complexmatrix *)
					malloc(sizeof(vartype_complexmatrix));
	    if (cm2 == NULL)
		return NULL;
	    cm2->type = TYPE_COMPLEXMATRIX;
	    cm2->rows = cm->rows;
	    cm2->columns = cm->columns;
	    cm2->array = cm->array;
	    cm->array->refcount++;
	    return (vartype *) cm2;
	}
	case TYPE_STRING: {
	    vartype_string *s = (vartype_string *) v;
	    return new_string(s->text, s->length);
	}
	default:
	    return NULL;
    }
}

int disentangle(vartype *v) {
    switch (v->type) {
	case TYPE_REALMATRIX: {
	    vartype_realmatrix *rm = (vartype_realmatrix *) v;
	    if (rm->array->refcount == 1)
		return 1;
	    else {
		realmatrix_data *md = (realmatrix_data *)
					malloc(sizeof(realmatrix_data));
		if (md == NULL)
		    return 0;
		int4 sz = rm->rows * rm->columns;
		int4 i;
		md->data = (phloat *) malloc(sz * sizeof(phloat));
		if (md->data == NULL) {
		    free(md);
		    return 0;
		}
		md->is_string = (char *) malloc(sz);
		if (md->is_string == NULL) {
		    free(md->data);
		    free(md);
		    return 0;
		}
		for (i = 0; i < sz; i++)
		    md->data[i] = rm->array->data[i];
		for (i = 0; i < sz; i++)
		    md->is_string[i] = rm->array->is_string[i];
		md->refcount = 1;
		rm->array->refcount--;
		rm->array = md;
		return 1;
	    }
	}
	case TYPE_COMPLEXMATRIX: {
	    vartype_complexmatrix *cm = (vartype_complexmatrix *) v;
	    if (cm->array->refcount == 1)
		return 1;
	    else {
		complexmatrix_data *md = (complexmatrix_data *)
					    malloc(sizeof(complexmatrix_data));
		if (md == NULL)
		    return 0;
		int4 sz = cm->rows * cm->columns * 2;
		int4 i;
		md->data = (phloat *) malloc(sz * sizeof(phloat));
		if (md->data == NULL) {
		    free(md);
		    return 0;
		}
		for (i = 0; i < sz; i++)
		    md->data[i] = cm->array->data[i];
		md->refcount = 1;
		cm->array->refcount--;
		cm->array = md;
		return 1;
	    }
	}
	case TYPE_REAL:
	case TYPE_COMPLEX:
	case TYPE_STRING:
	default:
	    return 1;
    }
}

int lookup_var(const char *name, int namelength) {
    /* TODO: memoize lookups.
     * Also make sure that purge_var updates the lookup cache!
     */
    int i, j;
    for (i = 0; i < vars_count; i++) {
	if (vars[i].length == namelength) {
	    for (j = 0; j < namelength; j++)
		if (vars[i].name[j] != name[j])
		    goto nomatch;
	    return i;
	}
	nomatch:;
    }
    return -1;
}

vartype *recall_var(const char *name, int namelength) {
    int varindex = lookup_var(name, namelength);
    if (varindex == -1)
	return NULL;
    else
	return vars[varindex].value;
}

void store_var(const char *name, int namelength, vartype *value) {
    int varindex = lookup_var(name, namelength);
    int i;
    if (varindex == -1) {
	if (vars_count == vars_capacity) {
	    vars_capacity += 25;
	    vars = (var_struct *)
			realloc(vars, vars_capacity * sizeof(var_struct));
	    // TODO - handle memory allocation failure
	}
	varindex = vars_count++;
	vars[varindex].length = namelength;
	for (i = 0; i < namelength; i++)
	    vars[varindex].name[i] = name[i];
    } else {
	if (matedit_mode != 0 &&
		string_equals(name, namelength, matedit_name, matedit_length)) {
	    /* We're replacing the indexed matrix; need to reset I and J */
	    /* TODO: proper handling of modes 2 and 3 (edit & editn); this code
	     * really only takes care of the mode 1 case, because it does not
	     * deal with the contents of X, nor with leaving the editor if the
	     * replacement value is not a matrix (or is that forbidden? Maybe
	     * that should cause a "Restricted Operation" error... And that
	     * means I'll have to fix store_var() so that it returns an error
	     * code -- which it should anyway; right now it does not check for
	     * memory allocation failure when growing the variables array, and
	     * that is evil).
	     */
	    if (value->type == TYPE_REALMATRIX
			|| value->type == TYPE_COMPLEXMATRIX)
		matedit_i = matedit_j = 0;
	    else
		matedit_mode = 0;
	}
	free_vartype(vars[varindex].value);
    }
    vars[varindex].value = value;
    update_catalog();
}

int purge_var(const char *name, int namelength) {
    int varindex = lookup_var(name, namelength);
    int i;
    if (varindex == -1)
	return 0;
    free_vartype(vars[varindex].value);
    for (i = varindex; i < vars_count - 1; i++)
	vars[i] = vars[i + 1];
    vars_count--;
    update_catalog();
    return 1;
}

void purge_all_vars() {
    int i;
    for (i = 0; i < vars_count; i++)
	free_vartype(vars[i].value);
    vars_count = 0;
}

int vars_exist(int real, int cpx, int matrix) {
    int i;
    for (i = 0; i < vars_count; i++) {
	switch (vars[i].value->type) {
	    case TYPE_REAL:
	    case TYPE_STRING:
		if (real)
		    return 1;
		else
		    break;
	    case TYPE_COMPLEX:
		if (cpx)
		    return 1;
		else
		    break;
	    case TYPE_REALMATRIX:
	    case TYPE_COMPLEXMATRIX:
		if (matrix)
		    return 1;
		else
		    break;
	}
    }
    return 0;
}

int contains_no_strings(const vartype_realmatrix *rm) {
    int4 size = rm->rows * rm->columns;
    int4 i;
    for (i = 0; i < size; i++)
	if (rm->array->is_string[i])
	    return 0;
    return 1;
}

int matrix_copy(vartype *dst, const vartype *src) {
    int4 size, i;
    if (src->type == TYPE_REALMATRIX) {
	vartype_realmatrix *s = (vartype_realmatrix *) src;
	if (dst->type == TYPE_REALMATRIX) {
	    vartype_realmatrix *d = (vartype_realmatrix *) dst;
	    if (s->rows != d->rows || s->columns != d->columns)
		return ERR_DIMENSION_ERROR;
	    size = s->rows * s->columns;
	    for (i = 0; i < size; i++) {
		d->array->is_string[i] = s->array->is_string[i];
		d->array->data[i] = s->array->data[i];
	    }
	    return ERR_NONE;
	} else if (dst->type == TYPE_COMPLEXMATRIX) {
	    vartype_complexmatrix *d = (vartype_complexmatrix *) dst;
	    if (s->rows != d->rows || s->columns != d->columns)
		return ERR_DIMENSION_ERROR;
	    if (!contains_no_strings(s))
		return ERR_ALPHA_DATA_IS_INVALID;
	    size = s->rows * s->columns;
	    for (i = 0; i < size; i++) {
		d->array->data[2 * i] = s->array->data[i];
		d->array->data[2 * i + 1] = 0;
	    }
	    return ERR_NONE;
	} else
	    return ERR_INVALID_TYPE;
    } else if (src->type == TYPE_COMPLEXMATRIX
		    && dst->type == TYPE_COMPLEXMATRIX) {
	vartype_complexmatrix *s = (vartype_complexmatrix *) src;
	vartype_complexmatrix *d = (vartype_complexmatrix *) dst;
	if (s->rows != d->rows || s->columns != d->columns)
	    return ERR_DIMENSION_ERROR;
	size = s->rows * s->columns * 2;
	for (i = 0; i < size; i++)
	    d->array->data[i] = s->array->data[i];
	return ERR_NONE;
    } else
	return ERR_INVALID_TYPE;
}
